﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

/// <summary>
/// 表达式树 模型转化 性能效率高于jsonconver 高于automapper 注意第一次调用由于需要生成表达式树并缓存会略微慢点，第二次调用会很快
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <typeparam name="TTarget"></typeparam>
internal static class ObjectConvertExtensions<TSource, TTarget>
{

    public readonly static Func<TSource, TTarget> MapFunc = GetMapFunc();

    public readonly static Action<TSource, TTarget> MapAction = GetMapAction();

    /// <summary>
    /// 将对象TSource转换为TTarget
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    public static TTarget Map(TSource source) => MapFunc(source);

    public static List<TTarget> MapList(IEnumerable<TSource> sources)
    {
        if (MapFunc == null) return null;

        return sources.Select(MapFunc).ToList();
    }



    /// <summary>
    /// 将对象TSource的值赋给给TTarget
    /// </summary>
    /// <param name="source"></param>
    /// <param name="target"></param>
    public static void Map(TSource source, TTarget target) => MapAction(source, target);



    private static Func<TSource, TTarget> GetMapFunc()
    {

        try
        {
            var sourceType = typeof(TSource);
            var targetType = typeof(TTarget);
            //Func委托传入变量
            var parameter = Expression.Parameter(sourceType, "p");

            var memberBindings = new List<MemberBinding>();

            var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
            foreach (var targetItem in targetTypes)
            {
                var sourceItem = sourceType.GetProperty(targetItem.Name);

                //判断实体的读写权限
                if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic) continue;

                //标注NotMapped特性的属性忽略转换
                if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null) continue;

                var sourceProperty = Expression.Property(parameter, sourceItem);
                if (sourceProperty == null) continue;

                if (sourceItem.PropertyType != targetItem.PropertyType)
                {

                    //判断都是(非泛型)class
                    if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                        !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                    {
                        var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        memberBindings.Add(Expression.Bind(targetItem, expression));
                        continue;
                    }

                    //集合数组类型的转换
                    if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                    {
                        var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                        memberBindings.Add(Expression.Bind(targetItem, expression));
                        continue;
                    }

                    var diffProExpression = GetDiffPropertyExpression(sourceProperty, sourceItem, sourceItem.PropertyType, targetItem.PropertyType, parameter);
                    if (diffProExpression != null)
                    {
                        memberBindings.Add(Expression.Bind(targetItem, diffProExpression));
                    }
                    continue;
                }
                memberBindings.Add(Expression.Bind(targetItem, sourceProperty));

            }
            //创建一个if条件表达式
            var test = Expression.NotEqual(parameter, Expression.Constant(null, sourceType));// p==null;
            var ifTrue = Expression.MemberInit(Expression.New(targetType), memberBindings);
            var condition = Expression.Condition(test, ifTrue, Expression.Constant(null, targetType));

            var lambda = Expression.Lambda<Func<TSource, TTarget>>(condition, parameter);

            return lambda.Compile();
        }
        catch (Exception ex)
        {
            throw new Exception($"表达式转换异常：{ex.Message}");
        }
    }

    private static MethodInfo stringToDateTimeMethod => typeof(DateTime).GetMethod("Parse", new[] { typeof(string) });
    private static MethodInfo int32ToStringMethod => typeof(Int32).GetMethod("ToString", new Type[0]);
    private static MethodInfo int64ToStringMethod => typeof(Int64).GetMethod("ToString", new Type[0]);
    private static MethodInfo decimalToStringMethod => typeof(decimal).GetMethod("ToString", new Type[0]);
    private static MethodInfo doubleToStringMethod => typeof(double).GetMethod("ToString", new Type[0]);
    private static MethodInfo floatToStringMethod => typeof(float).GetMethod("ToString", new Type[0]);
    private static MethodInfo charToStringMethod => typeof(char).GetMethod("ToString", new Type[0]);
    private static MethodInfo shortToStringMethod => typeof(short).GetMethod("ToString", new Type[0]);
    private static MethodInfo byteToStringMethod => typeof(byte).GetMethod("ToString", new Type[0]);
    private static MethodInfo dateTimeTostringMethod => typeof(DateTime).GetMethod("ToString", new Type[] { typeof(string) }, new ParameterModifier[] { new ParameterModifier(1) });
    private static MethodInfo enumToIntMethod => typeof(System.Enum).GetMethod("GetHashCode");



    /// <summary>
    /// 类型为集合时赋值
    /// </summary>
    /// <param name="sourceProperty"></param>
    /// <param name="targetProperty"></param>
    /// <param name="sourceType"></param>
    /// <param name="targetType"></param>
    /// <returns></returns>
    private static Expression GetDiffPropertyExpression(Expression sourceProperty, PropertyInfo propertyInfo, Type sourceType, Type targetType, ParameterExpression parameterExpression)
    {
        try
        {

            MethodInfo methodInfo = GetMethodInfo(sourceType, targetType);
            if (methodInfo == null)
                return null;

            var newSourceProperty = Expression.Property(parameterExpression, propertyInfo);

            if (string.Equals(targetType.Name, "String"))
            {
                if (string.Equals(sourceType.Name, "DateTime"))
                {
                    ConstantExpression constantReference = Expression.Constant("yyyy-MM-dd HH:mm:ss");
                    return Expression.Call(sourceProperty, methodInfo, constantReference);
                }
                return Expression.Call(sourceProperty, methodInfo);

            }

            return Expression.Call(methodInfo, newSourceProperty);
        }
        catch (Exception ex)
        {
            throw new Exception($"ObjectConvertExtensions_GetDiffPropertyExpression_{sourceType.Name}转{targetType.Name}有问题!{ex.Message}");
        }

    }

    private static MethodInfo GetMethodInfo(Type sourceType, Type targetType)
    {
        MethodInfo methodInfo = null;
        switch (targetType.Name)
        {
            case "String":
                methodInfo = GetToStringMethodInfo(sourceType);
                break;
            case "Int32":
                methodInfo = GetToInt32MethodInfo(sourceType);
                break;
            case "Int64":
                methodInfo = GetToInt64MethodInfo(sourceType);
                break;
            case "Decimal":
                methodInfo = GetToDecimalMethodInfo(sourceType);
                break;
            case "Double":
                methodInfo = GetToDoubleMethodInfo(sourceType);
                break;
            case "Single":
                methodInfo = GetToSingleMethodInfo(sourceType);
                break;
            case "Byte":
                methodInfo = GetToByteMethodInfo(sourceType);
                break;
            case "Int16":
                methodInfo = GetToInt16MethodInfo(sourceType);
                break;
            case "SByte":
                methodInfo = GetToSbyteMethodInfo(sourceType);
                break;
            case "Char":
                methodInfo = GetToCharMethodInfo(sourceType);
                break;
            case "DateTime":
                methodInfo = stringToDateTimeMethod;
                break;

        }
        return methodInfo;
    }



    private static MethodInfo GetToSingleMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("String", sourceName)
                || string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Char", sourceName))
        {
            return typeof(Convert).GetMethod("ToSingle", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToInt16MethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("String", sourceName)
                || string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Single", sourceName)
                || string.Equals("Char", sourceName))
        {
            return typeof(Convert).GetMethod("ToInt16", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToCharMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("String", sourceName)
                || string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Single", sourceName))
        {
            return typeof(Convert).GetMethod("ToChar", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToByteMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Char", sourceName)
            || string.Equals("SByte", sourceName)
            || string.Equals("Short", sourceName)
            || string.Equals("String", sourceName)
            || string.Equals("Int32", sourceName)
            || string.Equals("Int64", sourceName)
            || string.Equals("Decimal", sourceName)
            || string.Equals("Double", sourceName)
            || string.Equals("Single", sourceName))
        {
            return typeof(Convert).GetMethod("ToByte", new[] { sourcetype });
        }
        return null;
    }
    private static MethodInfo GetToSbyteMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Char", sourceName)
                || string.Equals("Byte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Double", sourceName)
                || string.Equals("Single", sourceName))
        {
            return typeof(Convert).GetMethod("ToSByte", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToDoubleMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Char", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Decimal", sourceName)
                || string.Equals("Single", sourceName))
        {
            return typeof(Convert).GetMethod("ToDouble", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToDecimalMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
                || string.Equals("Short", sourceName)
                || string.Equals("Char", sourceName)
                || string.Equals("String", sourceName)
                || string.Equals("Int32", sourceName)
                || string.Equals("Int64", sourceName)
                || string.Equals("Single", sourceName)
                || string.Equals("Double", sourceName))
        {
            return typeof(Convert).GetMethod("ToDecimal", new[] { sourcetype });
        }
        return null;
    }



    private static MethodInfo GetToInt64MethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Byte", sourceName)
            || string.Equals("SByte", sourceName)
            || string.Equals("Short", sourceName)
            || string.Equals("Int32", sourceName)
            || string.Equals("Char", sourceName)
            || string.Equals("String", sourceName)
            || string.Equals("Single", sourceName)
            || string.Equals("Decimal", sourceName)
            || string.Equals("Double", sourceName))
        {
            return typeof(Convert).GetMethod("ToInt64", new[] { sourcetype });
        }
        return null;
    }

    private static MethodInfo GetToInt32MethodInfo(Type sourcetype)
    {
        //if (sourcetype.IsEnum) //暂不支持枚举转换
        //{
        //    return typeof(Convert).GetMethod("ToInt32", new[] { sourcetype });
        //}
        string sourceName = sourcetype.Name;
        if (string.Equals("Byte", sourceName)
                || string.Equals("SByte", sourceName)
            || string.Equals("Short", sourceName)
            || string.Equals("Char", sourceName)
            || string.Equals("String", sourceName)
            || string.Equals("Int64", sourceName)
            || string.Equals("Decimal", sourceName)
            || string.Equals("Single", sourceName)
            || string.Equals("Double", sourceName))
        {
            return typeof(Convert).GetMethod("ToInt32", new[] { sourcetype });
        }

        return null;
    }



    private static MethodInfo GetToStringMethodInfo(Type sourcetype)
    {
        string sourceName = sourcetype.Name;
        if (string.Equals("Byte", sourceName)
            || string.Equals("SByte", sourceName)
            || string.Equals("Short", sourceName)
            || string.Equals("Char", sourceName)
            || string.Equals("Int32", sourceName)
            || string.Equals("Int64", sourceName)
            || string.Equals("Decimal", sourceName)
            || string.Equals("Double", sourceName)
            || string.Equals("Single", sourceName)
            || sourcetype.IsEnum)
        {
            return sourcetype.GetMethod("ToString", new Type[0]);
        }

        if (string.Equals("DateTime", sourcetype.Name))
        {
            return dateTimeTostringMethod;

        }
        return null;
    }

    /// <summary>
    /// 类型是clas时赋值
    /// </summary>
    /// <param name="sourceProperty"></param>
    /// <param name="targetProperty"></param>
    /// <param name="sourceType"></param>
    /// <param name="targetType"></param>
    /// <returns></returns>
    private static Expression GetClassExpression(Expression sourceProperty, Type sourceType, Type targetType)
    {
        try
        {
            //条件p.Item!=null    
            var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceType));

            //构造回调 Mapper<TSource, TTarget>.Map()
            var mapperType = typeof(ObjectConvertExtensions<,>).MakeGenericType(sourceType, targetType);
            var iftrue = Expression.Call(mapperType.GetMethod(nameof(Map), new[] { sourceType }), sourceProperty);

            var conditionItem = Expression.Condition(testItem, iftrue, Expression.Constant(null, targetType));

            return conditionItem;
        }
        catch (Exception ex)
        {
            throw new Exception($"ObjectConvertExtensions_GetClassExpression_{sourceProperty.ToString()}表达式树有问题!{ex.Message}");
        }

    }




    /// <summary>
    /// 类型为集合时赋值
    /// </summary>
    /// <param name="sourceProperty"></param>
    /// <param name="targetProperty"></param>
    /// <param name="sourceType"></param>
    /// <param name="targetType"></param>
    /// <returns></returns>
    private static Expression GetListExpression(Expression sourceProperty, Type sourceType, Type targetType)
    {
        try
        {
            //条件p.Item!=null    
            var testItem = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourceType));

            //构造回调 Mapper<TSource, TTarget>.MapList()
            var sourceArg = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments()[0];
            var targetArg = targetType.IsArray ? targetType.GetElementType() : targetType.GetGenericArguments()[0];
            var mapperType = typeof(ObjectConvertExtensions<,>).MakeGenericType(sourceArg, targetArg);

            var mapperExecMap = Expression.Call(mapperType.GetMethod(nameof(MapList), new[] { sourceType }), sourceProperty);

            Expression iftrue;
            if (targetType == mapperExecMap.Type)
            {
                iftrue = mapperExecMap;
            }
            else if (targetType.IsArray)//数组类型调用ToArray()方法
            {
                iftrue = Expression.Call(mapperExecMap, mapperExecMap.Type.GetMethod("ToArray"));
            }
            else if (typeof(IDictionary).IsAssignableFrom(targetType))
            {
                iftrue = Expression.Constant(null, targetType);//字典类型不转换
            }
            else
            {
                iftrue = Expression.Convert(mapperExecMap, targetType);
            }

            var conditionItem = Expression.Condition(testItem, iftrue, Expression.Constant(null, targetType));

            return conditionItem;
        }
        catch (Exception)
        {
            throw new Exception($"ObjectConvertExtensions_GetListExpression_{sourceProperty.ToString()}表达式树有问题!");
        }

    }

    private static Action<TSource, TTarget> GetMapAction()
    {

        var sourceType = typeof(TSource);
        var targetType = typeof(TTarget);
        //Func委托传入变量
        var sourceParameter = Expression.Parameter(sourceType, "p");

        var targetParameter = Expression.Parameter(targetType, "t");

        //创建一个表达式集合
        var expressions = new List<Expression>();

        var targetTypes = targetType.GetProperties().Where(x => x.PropertyType.IsPublic && x.CanWrite);
        foreach (var targetItem in targetTypes)
        {
            var sourceItem = sourceType.GetProperty(targetItem.Name);

            //判断实体的读写权限
            if (sourceItem == null || !sourceItem.CanRead || sourceItem.PropertyType.IsNotPublic)
                continue;

            //标注NotMapped特性的属性忽略转换
            if (sourceItem.GetCustomAttribute<NotMappedAttribute>() != null)
                continue;

            var sourceProperty = Expression.Property(sourceParameter, sourceItem);
            var targetProperty = Expression.Property(targetParameter, targetItem);

            //当非值类型且类型不相同时
            if (!sourceItem.PropertyType.IsValueType && sourceItem.PropertyType != targetItem.PropertyType)
            {
                //判断都是(非泛型)class
                if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                    !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                {
                    var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                    expressions.Add(Expression.Assign(targetProperty, expression));
                }

                //集合数组类型的转换
                if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                {
                    var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                    expressions.Add(Expression.Assign(targetProperty, expression));
                }

                continue;
            }

            if (sourceItem.PropertyType != targetItem.PropertyType)
            {

                //判断都是(非泛型)class
                if (sourceItem.PropertyType.IsClass && targetItem.PropertyType.IsClass &&
                    !sourceItem.PropertyType.IsGenericType && !targetItem.PropertyType.IsGenericType)
                {
                    var expression = GetClassExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                    expressions.Add(Expression.Assign(targetProperty, expression));
                    continue;
                }

                //集合数组类型的转换
                if (typeof(IEnumerable).IsAssignableFrom(sourceItem.PropertyType) && typeof(IEnumerable).IsAssignableFrom(targetItem.PropertyType))
                {
                    var expression = GetListExpression(sourceProperty, sourceItem.PropertyType, targetItem.PropertyType);
                    expressions.Add(Expression.Assign(targetProperty, expression));
                    continue;
                }

                var diffProExpression = GetDiffPropertyExpression(sourceProperty, sourceItem, sourceItem.PropertyType, targetItem.PropertyType, sourceParameter);
                if (diffProExpression != null)
                {
                    expressions.Add(Expression.Assign(targetProperty, diffProExpression));
                }
                continue;
            }

            expressions.Add(Expression.Assign(targetProperty, sourceProperty));
        }

        //当Target!=null判断source是否为空
        var testSource = Expression.NotEqual(sourceParameter, Expression.Constant(null, sourceType));
        var ifTrueSource = Expression.Block(expressions);
        var conditionSource = Expression.IfThen(testSource, ifTrueSource);

        //判断target是否为空
        var testTarget = Expression.NotEqual(targetParameter, Expression.Constant(null, targetType));
        var conditionTarget = Expression.IfThen(testTarget, conditionSource);

        var lambda = Expression.Lambda<Action<TSource, TTarget>>(conditionTarget, sourceParameter, targetParameter);
        return lambda.Compile();
    }


    public static TTarget ConvertTo(TSource source)
    {
        return MapFunc(source);
    }

    public static IEnumerable<TTarget> ConvertTo(IEnumerable<TSource> source)
    {
        if (source == null) return null;

        return source.Select(GetMapFunc());
    }
}

internal static class ObjectConvertExtensions
{
    public static TTarget ConvertTo<TSource, TTarget>(this TSource source)  
    {
        if (source == null) return default(TTarget);

        return ObjectConvertExtensions<TSource, TTarget>.ConvertTo(source);
    }
    public static IEnumerable<TTarget> ConvertTo<TSource, TTarget>(this IEnumerable<TSource> source)
    {
        if (source == null) return null;

        return ObjectConvertExtensions<TSource, TTarget>.ConvertTo(source);
    }

}











